package com.cx.auth.config;

import com.cx.auth.config.custom.sms.SmsAuthorizationServerTokenServices;
import com.cx.auth.config.custom.sms.SmsCodeTokenGranter;
import com.cx.auth.vo.vo.JwtAuthConfig;
import com.cx.auth.service.UserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author zhangcx
 * @Description TODO
 * @date 2022/6/6 13:54
 * @email zhangcx@bluemoon.com.cn
 */
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    private final DataSource dataSource;

    @Resource
    private JwtAuthConfig jwtAuthConfig;

    @Resource
    private UserDetailService userDetailsService;

    @Autowired
    private RedisTemplate redisTemplate;

    public AuthServerConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    @ConfigurationProperties(prefix = "jwt.auth")
    public JwtAuthConfig createJwtAuthConfig() {
        return new JwtAuthConfig();
    }

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private AuthorizationCodeServices authorizationCodeServices;

    @Resource
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    @Qualifier("commonTokenStore")
    private TokenStore commonTokenStore;

    @Autowired
    @Qualifier("smsTokenStore")
    private TokenStore smsTokenStore;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Resource(name="oauthClientDetailsService")
    private ClientDetailsService clientDetailsService;

    @Resource
    JwtTokenEnhancerConfig jwtTokenEnhancerConfig;

    @Resource
    private ApprovalStore approvalStore;

    @Autowired
    private SmsAuthorizationServerTokenServices smsAuthorizationServerTokenServices;

    /**
     * 将客户端信息存储到数据库  ,参照JdbcClientDetailsService源码里数据库建表
     * oauth_client_details
     * @return
     */
    @Bean("oauthClientDetailsService")
    public ClientDetailsService clientDetailsService(){
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 配置一个客户端
     * 既可以通过授权码获取令牌，也可以通过密码获取令牌(有四种方式)
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    //配置令牌访问端点
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
       endpoints
               .authenticationManager(authenticationManager)//认证管理(密码模式需要)
               .authorizationCodeServices(authorizationCodeServices)//授权码模式
               .tokenServices(tokenServices())//令牌管理服务
               .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)//允许post提交访问令牌
               .approvalStore(approvalStore)//授权信息保存策略
               .tokenGranter(tokenGranter(endpoints))//添加自定义授权类型
                ;
    }

    /**
     * 令牌的访问策略
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")//oauth/token_key是公开
                .checkTokenAccess("permitAll()") //oauth/check_token   已经验证了的客户端才能请求check_token 端点
                .allowFormAuthenticationForClients();//表单认证（申请令牌）
    }

    /**
     * 授权信息保存策略
     * 当前设置为存储到数据库的oauth_approvals表中
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        return new JdbcApprovalStore(dataSource);
    }


    /**
     * 设置授权码模式的授权码如何存取
     * 当前设置为存储到数据库的oauth_code表中
     * 扩展：可以自定义设置存储在redis中
     * @param
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * jwt实现
     * @return
     */
   /* @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情
        tokenServices.setSupportRefreshToken(true);//支持刷新令牌
        tokenServices.setTokenStore(tokenStore);//令牌存储策略-用户信息存储在jwt中
//        tokenServices.setTokenStore(new JdbcTokenStore(dataSource));//设置dataSource 会将token记录保存在oauth_access_token和oauth_refresh_token中
        //令牌增强 使用jwt令牌
        //使用jwt令牌来替代默认令牌，这样做的好处是携带默认令牌访问资源，每次都要通过授权服务来认证令牌是否有效，
        //而jwt则可以做到资源服务器中自己解析从而判断令牌的有效性；另外一个优势就是jwt令牌有更高的安全性，
        //可以使用公钥和私钥进行加密和解密，不容易被破解
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancerConfig,accessTokenConverter));
        tokenServices.setTokenEnhancer(tokenEnhancerChain);

        tokenServices.setAccessTokenValiditySeconds(7200); //令牌默认有效期是2小时
        tokenServices.setRefreshTokenValiditySeconds(9600);//刷新令牌默认有效期
        return tokenServices;
    }
    */

    /**
     * 读取本地密钥对
     */
    @Bean
    public KeyPair keyPair(){
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource(jwtAuthConfig.getCertificateStoreFile()),jwtAuthConfig.getPassword().toCharArray());
        KeyPair keyPair = factory.getKeyPair(jwtAuthConfig.getAlias(), jwtAuthConfig.getPassword().toCharArray());
        return keyPair;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //加载密钥
        converter.setKeyPair(keyPair());
        return converter;
    }


    /**
     * redis 实现
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(commonTokenStore);//令牌存储策略-用户信息存储在redis中
        tokenServices.setClientDetailsService(clientDetailsService);//客户端详情
        tokenServices.setSupportRefreshToken(true);//支持刷新令牌
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setAccessTokenValiditySeconds(7200); //令牌默认有效期是2小时
        tokenServices.setRefreshTokenValiditySeconds(9600);//刷新令牌默认有效期
        // 令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancerConfig,accessTokenConverter));
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        return tokenServices;
    }

    /**
     * 添加自定义授权类型
     *
     * @return List<TokenGranter>
     */
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {

        // endpoints.getTokenGranter() 获取SpringSecurity OAuth2.0 现有的授权类型
        List<TokenGranter> granters = new ArrayList<TokenGranter>(Collections.singletonList(endpoints.getTokenGranter()));

        // 构建短信验证授权类型
        SmsCodeTokenGranter smsCodeTokenGranter = new SmsCodeTokenGranter(smsAuthorizationServerTokenServices, endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(),userDetailsService,redisTemplate);
        // 向集合中添加短信授权类型
        granters.add(smsCodeTokenGranter);
        // 返回所有类型
        return new CompositeTokenGranter(granters);
    }
}
